XGBoost wrapper for seurat project in R
source("tianfengRwrappers.R")
载入需要的程辑包:dplyr
载入程辑包:‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
载入需要的程辑包:reticulate
载入需要的程辑包:tidyr
载入程辑包:‘MySeuratWrappers’
The following objects are masked from ‘package:Seurat’:
DimPlot, DoHeatmap, LabelClusters, RidgePlot, VlnPlot
载入程辑包:‘cowplot’
The following object is masked from ‘package:ggpubr’:
get_legend
载入需要的程辑包:viridisLite
载入程辑包:‘reshape2’
The following object is masked from ‘package:tidyr’:
smiths
NOTE: Either Arial Narrow or Roboto Condensed fonts are required to use these themes.
Please use hrbrthemes::import_roboto_condensed() to install Roboto Condensed and
if Arial Narrow is not on your system, please see https://bit.ly/arialnarrow
Registered S3 method overwritten by 'enrichplot':
method from
fortify.enrichResult DOSE
clusterProfiler v3.14.3 For help: https://guangchuangyu.github.io/software/clusterProfiler
If you use clusterProfiler in published research, please cite:
Guangchuang Yu, Li-Gen Wang, Yanyan Han, Qing-Yu He. clusterProfiler: an R package for comparing biological themes among gene clusters. OMICS: A Journal of Integrative Biology. 2012, 16(5):284-287.
Registering fonts with R
载入程辑包:‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
载入需要的程辑包:Biobase
载入需要的程辑包:BiocGenerics
载入需要的程辑包:parallel
载入程辑包:‘BiocGenerics’
The following objects are masked from ‘package:parallel’:
clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport, clusterMap, parApply,
parCapply, parLapply, parLapplyLB, parRapply, parSapply, parSapplyLB
The following objects are masked from ‘package:dplyr’:
combine, intersect, setdiff, union
The following objects are masked from ‘package:stats’:
IQR, mad, sd, var, xtabs
The following objects are masked from ‘package:base’:
anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated,
eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match,
mget, order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames,
sapply, setdiff, sort, table, tapply, union, unique, unsplit, which, which.max, which.min
Welcome to Bioconductor
Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor, see
'citation("Biobase")', and for packages 'citation("pkgname")'.
载入需要的程辑包:e1071
载入程辑包:‘widgetTools’
The following object is masked from ‘package:dplyr’:
funs
载入程辑包:‘DynDoc’
The following object is masked from ‘package:BiocGenerics’:
path
载入程辑包:‘DT’
The following object is masked from ‘package:Seurat’:
JS
========================================
circlize version 0.4.13
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: https://jokergoo.github.io/circlize_book/book/
If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization
in R. Bioinformatics 2014.
This message can be suppressed by:
suppressPackageStartupMessages(library(circlize))
========================================
载入需要的程辑包:grid
========================================
ComplexHeatmap version 2.2.0
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference
If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional
genomic data. Bioinformatics 2016.
========================================
载入程辑包:‘ComplexHeatmap’
The following object is masked from ‘package:plotly’:
add_heatmap
library(xgboost)
载入程辑包:‘xgboost’
The following object is masked from ‘package:plotly’:
slice
The following object is masked from ‘package:dplyr’:
slice
library(Matrix)
载入程辑包:‘Matrix’
The following objects are masked from ‘package:tidyr’:
expand, pack, unpack
library(mclust)
__ ___________ __ _____________
/ |/ / ____/ / / / / / ___/_ __/
/ /|_/ / / / / / / / /\__ \ / /
/ / / / /___/ /___/ /_/ /___/ // /
/_/ /_/\____/_____/\____//____//_/ version 5.4.9
Type 'citation("mclust")' for citing this R package in publications.
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
Registered S3 method overwritten by 'cli':
method from
print.boxx spatstat
─ Attaching packages ───────────────────────────────────── tidyverse 1.3.1 ─
✓ tibble 3.1.5 ✓ stringr 1.4.0
✓ readr 2.1.2 ✓ forcats 0.5.1
✓ purrr 0.3.4
─ Conflicts ─────────────────────────────────────── tidyverse_conflicts() ─
x Biobase::combine() masks BiocGenerics::combine(), dplyr::combine()
x Matrix::expand() masks tidyr::expand()
x plotly::filter() masks dplyr::filter(), stats::filter()
x widgetTools::funs() masks dplyr::funs()
x dplyr::lag() masks stats::lag()
x purrr::map() masks mclust::map()
x Matrix::pack() masks tidyr::pack()
x BiocGenerics::Position() masks ggplot2::Position(), base::Position()
x purrr::simplify() masks clusterProfiler::simplify()
x xgboost::slice() masks plotly::slice(), dplyr::slice()
x Matrix::unpack() masks tidyr::unpack()
library(SHAPforxgboost)
library(lambda.r)
载入程辑包:‘lambda.r’
The following object is masked from ‘package:reticulate’:
%as%
ds0 <- readRDS("ds0.rds")
ds1 <- readRDS("ds1.rds")
ds2 <- readRDS("ds2.rds")
XGBoost_predict_from_seuobj <- function(seuobj, bst_model, is_highvar = T, seed = 7, celltype_assign = 2)
{
#return a updated seurat object with new metadata named confidence and projected_idents
seuobj_label <- as.numeric(as.character(Idents(seuobj)))
if(is.na(seuobj_label[1])) # check vaild Idents
{
stop("Please ensure that seurat idents are in numeric forms")
}
temp <- get_data_table(seuobj, highvar = T, type = "data")
seuobj_data <- matrix(data = 0, nrow = bst_model$nfeatures, ncol = length(colnames(temp)),
byrow = FALSE, dimnames = list(bst_model[["feature_names"]],colnames(temp)))
intersect_features <- intersect(bst_model[["feature_names"]], rownames(temp))
seuobj_data[intersect_features,] <- temp[intersect_features,]
rm(temp)
# colnames(seuobj_data) <- NULL
seuobj_test_data <- list(data = t(as(seuobj_data,"dgCMatrix")), label = seuobj_label)
seuobj_test <- xgb.DMatrix(data = seuobj_test_data$data,label = seuobj_test_data$label)
#预测结果
predict_seuobj_test <- predict(bst_model, newdata = seuobj_test)
predict_prop_seuobj <<- matrix(data=predict_seuobj_test, nrow = bst_model[["params"]][["num_class"]],
ncol = ncol(seuobj), byrow = FALSE,
dimnames = list(as.character(0:(bst_model[["params"]][["num_class"]]-1)),
colnames(seuobj)))
# predict cell types
if(celltype_assign = 1){
错误: 意外的'=' in:
" # predict cell types
if(celltype_assign ="
function instance
predict
Idents(ds0) <- ds0$seurat_clusters
ds0 <- XGBoost_predict_from_seuobj(ds0, bst_model = bst_model) %>%
project2ref_celltype(ref_seuobj = ds2, ref_labels = c("seurat_clusters","Classification1"))
ds1 <- XGBoost_predict_from_seuobj(ds1, bst_model = bst_model) %>%
project2ref_celltype(ref_seuobj = ds2, ref_labels = c("seurat_clusters","Classification1"))
umapplot(ds0, group.by = "ref_celltype")
scmap wrapper
library(SingleCellExperiment)
library(scmap)
mkref_scmap_from_seuobj <- function(ref_seuobj){
# return a ref sce object
ref_sce <- as.SingleCellExperiment(ref_seuobj)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)
counts(ref_sce) <- as.matrix(counts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))
rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = T) %>% indexCell()
return(ref_sce)
}
query_scmap_from_refsce <- function(query_seuobj, ref_sce, ref_labels = 'Classification1'){
#return a updated seurat object with new metadata named scmap_idents
query_sce <- as.SingleCellExperiment(query_seuobj)
logcounts(query_sce) <- log2(counts(query_sce) + 1)
counts(query_sce) <- as.matrix(counts(query_sce))
logcounts(query_sce) <- as.matrix(logcounts(query_sce))
rowData(query_sce)$feature_symbol <- rownames(query_sce)
query_sce <- query_sce[!duplicated(rownames(query_sce)), ]
query_sce <- selectFeatures(query_sce, suppress_plot = T) %>% indexCell()
scmapCell_results <- scmapCell(query_sce, list(ref = metadata(ref_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(scmapCell_results,
list(as.character(colData(ref_sce)[[ref_labels]])))
query_seuobj$scmap_idents <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ref"],
row.names = colnames(query_seuobj))
return(query_seuobj)
}
ds1 <- query_scmap_from_refsce(ds1, ref_sce)
Warning: 'isSpike' is deprecated.
See help("Deprecated")
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Warning: stack imbalance in '<-', 2 then 1
supervised vs unsupervised clustering
upset plot
SMC2
# library(UpSetR)
df <- cbind(ds1[["Classification1"]],ds1[["ref_celltype"]],ds1[["scmap_idents"]])
df <- df[df$Classification1 == "SMC2" | df$ref_celltype == "SMC2" | df$scmap_idents == "SMC2" ,]
li <- table(df) %>% as.data.frame() #获得含有SMC2的frequency
li <- li[!(li$Freq < 5),] #删除frequency<5的行
li <- li[order(li$Freq,decreasing = T),]
dd <- data.frame(li,index = as.character(1:nrow(li)))
dd$index <- factor(dd$index,levels = 1:length(levels(dd$index)))
dd$Freq <- NULL
dd <- reshape2::melt(dd,id.var = "index")
colnames(dd) <- c("index","type","name")
dt <- data.frame(Freq = li$Freq,index = as.character(1:nrow(li)))
dt$index <- factor(dt$index,levels = 1:length(levels(dt$index)))
dt$col <- dt$Freq>(sum(dt$Freq)/10) #set the color to red for those frequency > 0.1
p1 <- ggplot(dd)+geom_point(mapping = aes(x = index, y = type, color = name), size = 6) + mytheme2 + scale_color_manual(values = colors_list) + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank())
p2 <- ggplot(dt,aes(x = index, y = Freq, fill = col))+geom_bar(stat = "identity") + mytheme2 + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank(), legend.position = 'none') + scale_fill_manual(values = c("black","red"))
plot <- cowplot::plot_grid(p2,p1,ncol = 1,align = 'v',rel_heights = c(2,1))
plot
# ggsave("SMC2cells.svg",plot=plot,device = svg,height = 8,width = 8)
SMC1
df <- cbind(ds1[["Classification1"]],ds1[["ref_celltype"]],ds1[["scmap_idents"]])
df <- df[df$Classification1 == "SMC1" | df$ref_celltype == "SMC1" | df$scmap_idents == "SMC1" ,]
li <- table(df) %>% as.data.frame() #获得含有SMC1的frequency
li <- li[!(li$Freq < 5),] #删除frequency<5的行
li <- li[order(li$Freq,decreasing = T),]
dd <- data.frame(li,index = as.character(1:nrow(li)))
dd$index <- factor(dd$index,levels = 1:length(levels(dd$index)))
dd$Freq <- NULL
dd <- reshape2::melt(dd,id.var = "index")
colnames(dd) <- c("index","type","name")
dt <- data.frame(Freq = li$Freq,index = as.character(1:nrow(li)))
dt$index <- factor(dt$index,levels = 1:length(levels(dt$index)))
dt$col <- dt$Freq>(sum(dt$Freq)/10) #set the color to red for those frequency > 0.1
p1 <- ggplot(dd)+geom_point(mapping = aes(x = index, y = type, color = name), size = 6) + mytheme2 + scale_color_manual(values = colors_list) + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank())
p2 <- ggplot(dt,aes(x = index, y = Freq, fill = col))+geom_bar(stat = "identity") + mytheme2 + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank(), legend.position = 'none') + scale_fill_manual(values = c("black","red"))
plot <- cowplot::plot_grid(p2,p1,ncol = 1,align = 'v',rel_heights = c(2,1))
plot
ggsave("SMC1cells.svg",plot=plot,device = svg,height = 8,width = 8)
FBM
df <- cbind(ds1[["Classification1"]],ds1[["ref_celltype"]],ds1[["scmap_idents"]])
df <- df[df$Classification1 == "Fibromyocyte" | df$ref_celltype == "Fibromyocyte" | df$scmap_idents == "Fibromyocyte" ,]
li <- table(df) %>% as.data.frame() #获得含有Fibromyocyte的frequency
li <- li[!(li$Freq < 5),] #删除frequency<5的行
li <- li[order(li$Freq,decreasing = T),]
dd <- data.frame(li,index = as.character(1:nrow(li)))
dd$index <- factor(dd$index,levels = 1:length(levels(dd$index)))
dd$Freq <- NULL
dd <- reshape2::melt(dd,id.var = "index")
colnames(dd) <- c("index","type","name")
dt <- data.frame(Freq = li$Freq,index = as.character(1:nrow(li)))
dt$index <- factor(dt$index,levels = 1:length(levels(dt$index)))
dt$col <- dt$Freq>(sum(dt$Freq)/10) #set the color to red for those frequency > 0.1
p1 <- ggplot(dd)+geom_point(mapping = aes(x = index, y = type, color = name), size = 6) + mytheme2 + scale_color_manual(values = colors_list) + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank())
p2 <- ggplot(dt,aes(x = index, y = Freq, fill = col))+geom_bar(stat = "identity") + mytheme2 + theme(axis.ticks.x = element_blank(),axis.title.x = element_blank(), legend.position = 'none') + scale_fill_manual(values = c("black","red"))
plot <- cowplot::plot_grid(p2,p1,ncol = 1,align = 'v',rel_heights = c(2,1))
plot
ggsave("Fibromyocytecells.svg",plot=plot,device = svg,height = 8,width = 8)
correlation plot

unsupervised version
ds1 <- XGBoost_predict_from_seuobj(ds1,bst_model)
Warning in XGBoost_predict_from_seuobj(ds1, bst_model) :
Please ensure that seurat idents are in numeric forms
[1] "ARI = 0.306022542455434"
[1] "return a seurat object with meta.data'X1'~'Xn'"
[1] "return a seurat object with meta.data'projected_idents'"
ds1 <- XGBoost_predict_from_seuobj(ds1,bst_model)
Warning in XGBoost_predict_from_seuobj(ds1, bst_model) :
Please ensure that seurat idents are in numeric forms
[1] "ARI = 0.306022542455434"
[1] "return a seurat object with meta.data'X1'~'Xn'"
[1] "return a seurat object with meta.data'projected_idents'"
ds2 <- XGBoost_predict_from_seuobj(ds2,bst_model)
Warning in XGBoost_predict_from_seuobj(ds2, bst_model) :
强制改变过程中产生了NA
Warning in XGBoost_predict_from_seuobj(ds2, bst_model) :
Please ensure that seurat idents are in numeric forms
[1] "ARI = 0.744493360267919"
[1] "return a seurat object with meta.data'X1'~'Xn'"
[1] "return a seurat object with meta.data'projected_idents'"
umapplot(ds0,"projected_idents") /
umapplot(ds1,"projected_idents") /
umapplot(ds2,"projected_idents")
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the
existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the
existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the
existing scale.

# mergeds <- mergeds %>% SCTransform(vars.to.regress = "percent.mt", verbose = F) %>%
# RunPCA() %>% FindNeighbors(dims = 1:20) %>%
# RunUMAP(dims = 1:20) %>%
# FindClusters(resolution = 0.1)
#
# umapplot(mergeds,group.by = "Classification1" ,split.by = "orig.ident")
Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.
When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.
LS0tCnRpdGxlOiAiWEdCb29zdCB3cmFwcGVyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFhHQm9vc3Qgd3JhcHBlciBmb3Igc2V1cmF0IHByb2plY3QgaW4gUgoKYGBge3J9CnNvdXJjZSgidGlhbmZlbmdSd3JhcHBlcnMuUiIpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShTSEFQZm9yeGdib29zdCkKbGlicmFyeShsYW1iZGEucikKCmRzMCA8LSByZWFkUkRTKCJkczAucmRzIikKZHMxIDwtIHJlYWRSRFMoImRzMS5yZHMiKQpkczIgPC0gcmVhZFJEUygiZHMyLnJkcyIpCmBgYAoKCmBgYHtyfQpYR0Jvb3N0X3RyYWluX2Zyb21fc2V1b2JqIDwtIGZ1bmN0aW9uKHNldW9iaiwgaXNfaGlnaHZhciA9IFQsIHRlc3RfcmF0aW8gPSAwLjMsIHNlZWQgPSA3KQp7IAogICMjIHNldCB0ZXN0X3JhdGlvIHRvIDAgdG8gYXZvaWQgZXh0cmFjdGluZyB0ZXN0IGZyb20gZGF0YXNldAogIHNldC5zZWVkKHNlZWQpCiAgc2V1b2JqX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhzZXVvYmopKSkKICBpZihpcy5uYShzZXVvYmpfbGFiZWxbMV0pKSAjIGNoZWNrIHZhaWxkIElkZW50cwogIHsKICAgIHN0b3AoIlBsZWFzZSBlbnN1cmUgdGhhdCBzZXVyYXQgaWRlbnRzIGFyZSBpbiBudW1lcmljIGZvcm1zIikKICB9CiAgIyBjb2xuYW1lcyhzZXVvYmpfZGF0YSkgPC0gTlVMTAogIHNldW9ial9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKHNldW9iaiwgaGlnaHZhciA9IFQsIHR5cGUgPSAiZGF0YSIpCiAgeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKHNldW9iaikpKSwKICAgICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdHByb2IiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCiAgCiAgaWYodGVzdF9yYXRpbyA9PSAwKSB7CiAgICBzZXVvYmpfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKHNldW9ial9kYXRhLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gc2V1b2JqX2xhYmVsKSAKICAgICMgdXNlIHdob2xlIGRhdGFzZXQgYXMgdHJhaW4gZGF0YQogICAgc2V1b2JqX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBzZXVvYmpfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gc2V1b2JqX3RyYWluX2RhdGEkbGFiZWwpCiAgICBic3RfbW9kZWwgPC0geGdiLnRyYWluKHhnYl9wYXJhbSwgc2V1b2JqX3RyYWluLCBucm91bmRzID0gMTAwLCB2ZXJib3NlID0gMCkKICB9IGVsc2UgewogICAgaW5kZXggPC0gYygxOmRpbShzZXVvYmpfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZyh0ZXN0X3JhdGlvKmRpbShzZXVvYmpfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCiAgICBzZXVvYmpfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKHNldW9ial9kYXRhWywtaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gc2V1b2JqX2xhYmVsWy1pbmRleF0pCiAgICBzZXVvYmpfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoc2V1b2JqX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IHNldW9ial9sYWJlbFtpbmRleF0pCiAgICBzZXVvYmpfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gc2V1b2JqX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gc2V1b2JqX3Rlc3RfZGF0YSRsYWJlbCkKICAgIHNldW9ial90cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gc2V1b2JqX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IHNldW9ial90cmFpbl9kYXRhJGxhYmVsKQogICAgd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBzZXVvYmpfdHJhaW4sIGV2YWwgPSBzZXVvYmpfdGVzdCkKICAgIGJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBzZXVvYmpfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCiAgfQogIHJldHVybihic3RfbW9kZWwpCn0KIyBzYXZlUkRTKGJzdF9tb2RlbCwgImRzMl9tb2RlbC5yZHMiKQoKc2hvd190cmFpbl9sb3NzIDwtIGZ1bmN0aW9uKGJzdF9tb2RlbCwgbnJvdW5kcyA9IDEwMCkgI3doZW4gJCB0ZXN0X3JhdGlvIFxuZXEgMCAkIHNob3cgbG9zcyBpbiB3YXRjaGxpc3QKewogIGV2YWxfbG9zcyA8LSBic3RfbW9kZWxbWyJldmFsdWF0aW9uX2xvZyJdXVtbImV2YWxfbWxvZ2xvc3MiXV0KICBwbG90X2x5KGRhdGEuZnJhbWUoZXZhbF9sb3NzKSwgeCA9IGMoMTpucm91bmRzKSwgeSA9IGV2YWxfbG9zcykgJT4lIAogICAgYWRkX3RyYWNlKHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibWFya2VycytsaW5lcyIsIAogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGQzciLCB3aWR0aCA9IDEpKSwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGODAiLCB3aWR0aCA9IDIpKSAlPiUgCiAgICBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gImVwb2NoIikseWF4aXMgPSBsaXN0KHRpdGxlID0gImV2YWxfbWxvZ2xvc3MiKSwgCiAgICAgICAgICAgdGl0bGUgPSAidHJhaW5fbG9zcyIsIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJBcmlhbCIsIHNpemUgPSAyNSwgY29sb3IgPSAiYmxhY2siKSkKfQoKWEdCb29zdF9wcmVkaWN0X2Zyb21fc2V1b2JqIDwtIGZ1bmN0aW9uKHNldW9iaiwgYnN0X21vZGVsLCBpc19oaWdodmFyID0gVCwgc2VlZCA9IDcsIGNlbGx0eXBlX2Fzc2lnbiA9IDIpCnsKICAjcmV0dXJuIGEgdXBkYXRlZCBzZXVyYXQgb2JqZWN0IHdpdGggbmV3IG1ldGFkYXRhIG5hbWVkIGNvbmZpZGVuY2UgYW5kIHByb2plY3RlZF9pZGVudHMKICBzZXVvYmpfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKHNldW9iaikpKQogIGlmKCFpcy5udWxsKHdoaWNoKGlzLm5hKHNldW9ial9sYWJlbCkpKSkgIyBjaGVjayB2YWlsZCBJZGVudHMKICB7CiAgICB3YXJuaW5nKCJQbGVhc2UgZW5zdXJlIHRoYXQgc2V1cmF0IGlkZW50cyBhcmUgaW4gbnVtZXJpYyBmb3JtcyIpICPov5nph4zkvLzkuY7kuI3ot7Plh7rkuZ/lj6/ku6UKICAgIHNldW9ial9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihzZXVvYmokc2V1cmF0X2NsdXN0ZXJzKSkKICB9CiAgdGVtcCA8LSBnZXRfZGF0YV90YWJsZShzZXVvYmosIGhpZ2h2YXIgPSBULCB0eXBlID0gImRhdGEiKQogIHNldW9ial9kYXRhIDwtIG1hdHJpeChkYXRhID0gMCwgbnJvdyA9IGJzdF9tb2RlbCRuZmVhdHVyZXMsIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBGQUxTRSwgZGltbmFtZXMgPSBsaXN0KGJzdF9tb2RlbFtbImZlYXR1cmVfbmFtZXMiXV0sY29sbmFtZXModGVtcCkpKQogIGludGVyc2VjdF9mZWF0dXJlcyA8LSBpbnRlcnNlY3QoYnN0X21vZGVsW1siZmVhdHVyZV9uYW1lcyJdXSwgcm93bmFtZXModGVtcCkpCiAgc2V1b2JqX2RhdGFbaW50ZXJzZWN0X2ZlYXR1cmVzLF0gPC0gdGVtcFtpbnRlcnNlY3RfZmVhdHVyZXMsXQogIHJtKHRlbXApCiAgCgogICMgY29sbmFtZXMoc2V1b2JqX2RhdGEpIDwtIE5VTEwKICBzZXVvYmpfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoc2V1b2JqX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBzZXVvYmpfbGFiZWwpCiAgc2V1b2JqX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHNldW9ial90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IHNldW9ial90ZXN0X2RhdGEkbGFiZWwpCiAgCiAgI+mihOa1i+e7k+aenAogIHByZWRpY3Rfc2V1b2JqX3Rlc3QgPC0gcHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBzZXVvYmpfdGVzdCkKICAKICBwcmVkaWN0X3Byb3Bfc2V1b2JqIDw8LSBtYXRyaXgoZGF0YT1wcmVkaWN0X3NldW9ial90ZXN0LCBucm93ID0gYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbmNvbChzZXVvYmopLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QoYXMuY2hhcmFjdGVyKDA6KGJzdF9tb2RlbFtbInBhcmFtcyJdXVtbIm51bV9jbGFzcyJdXS0xKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzKHNldW9iaikpKQoKICAjIHByZWRpY3QgY2VsbCB0eXBlcwogIGlmKGNlbGx0eXBlX2Fzc2lnbiA9PSAxKXsKICAgIHNldW9ial9yZXMgPC0gYXBwbHkocHJlZGljdF9wcm9wX3NldW9iaiwyLGlkZW50X2Fzc2lnbmZ1bmMscm93bmFtZXMocHJlZGljdF9wcm9wX3NldW9iaikpCiAgfWVsc2UgaWYoY2VsbHR5cGVfYXNzaWduID09IDIpewogICAgc2V1b2JqX3JlcyA8LSBhcHBseShwcmVkaWN0X3Byb3Bfc2V1b2JqLDIsaWRlbnRfYXNzaWduZnVuYzIscm93bmFtZXMocHJlZGljdF9wcm9wX3NldW9iaikpCiAgfQogIAogIHByaW50KHBhc3RlKCdBUkkgPScsYWRqdXN0ZWRSYW5kSW5kZXgoc2V1b2JqX3Jlcywgc2V1b2JqX3Rlc3RfZGF0YSRsYWJlbCkpKQogIAogIHNldW9iaiA8LSBBZGRNZXRhRGF0YShzZXVvYmosIGRhdGEuZnJhbWUodChwcmVkaWN0X3Byb3Bfc2V1b2JqKSwgc3RyaW5nc0FzRmFjdG9ycz1GKSkKICBwcmludCgicmV0dXJuIGEgc2V1cmF0IG9iamVjdCB3aXRoIG1ldGEuZGF0YSdYMSd+J1huJyIpCiAgCiAgI3NhdmUgYW5kIHVwZGF0ZSBzZXVyYXQgb2JqZWN0CiAgc2V1b2JqJHByb2plY3RlZF9pZGVudHMgPC0gZmFjdG9yKHNldW9ial9yZXMsIGxldmVscyA9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYyhhcy5jaGFyYWN0ZXIoMDooYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLTEpKSwidW5hc3NpZ25lZCIpKQogIHByaW50KCJyZXR1cm4gYSBzZXVyYXQgb2JqZWN0IHdpdGggbWV0YS5kYXRhJ3Byb2plY3RlZF9pZGVudHMnIikKICByZXR1cm4oc2V1b2JqKQp9CgojIyBhc3NpZ24gY2VsbCB0eXBlIHByZWRpY3RlZCB2aWEgdHJlZSBtb2RlbHMsIGNvbnNpZGVyIGNvbmZpZGVuY2UKaWRlbnRfYXNzaWduZnVuYyA8LSBmdW5jdGlvbihzLCBpZGVudCkgewogICAgaWYgKG1heChzKSA+IDEuNSAvIGxlbmd0aChpZGVudCkpIHsKICAgICAgICAgIHJldHVybihpZGVudFt3aGljaChzID09IG1heChzKSldKQogICAgICB9IGVsc2UgewogICAgICAgICAgcmV0dXJuKCJ1bmFzc2lnbmVkIikKICAgICAgfQp9CgppZGVudF9hc3NpZ25mdW5jMiA8LSBmdW5jdGlvbihzLCBpZGVudCkKIyBjb25maWRlbmNlIDogbWF4IC0gMnRoX21heCA+IDAuNAp7CiAgICBpZiAobWF4KHMpIC0gbWF4KHNbcyE9bWF4KHMpXSkgPiAwLjQpIHsKICAgICAgICAgIHJldHVybihpZGVudFt3aGljaChzID09IG1heChzKSldKQogICAgICB9IGVsc2UgewogICAgICAgICAgcmV0dXJuKCJ1bmFzc2lnbmVkIikKICAgICAgfQp9Cgpwcm9qZWN0MnJlZl9jZWxsdHlwZSA8LSBmdW5jdGlvbihxdWVyeV9zZXVvYmosIHJlZl9zZXVvYmosIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeV9sYWJlbHMgPSAicHJvamVjdGVkX2lkZW50cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZl9sYWJlbHMgPSBjKCJzZXVyYXRfY2x1c3RlcnMiLCJDbGFzc2lmaWNhdGlvbjEiKSkgCiAgIyB0cmFuc2ZlciBsYWJsZXM6IGFkZCByZWZfY2VsbHR5cGUgdG8gbWV0YS5kYXRhIGluIHF1ZXJ5IHNldXJhdCBvYmplY3QKewogIGlkZW50bWFwIDwtIGxldmVscyhyZWZfc2V1b2JqQG1ldGEuZGF0YVtbcmVmX2xhYmVsc1sxXV1dKSAKICAjIyBidWlsZCBtYXBwaW5nIGJldHdlZW4gbnVtZXJpYyBsYWJlbHMgYW5kIHJlZiBsYWJlbHMKICBuYW1lcyhpZGVudG1hcCkgPC0gbGV2ZWxzKHJlZl9zZXVvYmpAbWV0YS5kYXRhW1tyZWZfbGFiZWxzWzJdXV0pIAogIAogIGRmIDwtIHF1ZXJ5X3NldW9iakBtZXRhLmRhdGFbW3F1ZXJ5X2xhYmVsc11dCiAgbGFtYmRhKHgsaWRlbnRtYXApICU6PSUgaWZlbHNlKHg9PSJ1bmFzc2lnbmVkIix4LG5hbWVzKGlkZW50bWFwW2lkZW50bWFwID09IHhdKSkKICBsZXZlbHMoZGYpIDwtIGxhcHBseShsZXZlbHMoZGYpLCBsYW1iZGEsIGlkZW50bWFwKSAlPiUgYXMuY2hhcmFjdGVyKCkgIyBwZXJtdXRlIGNlbGwgbGFiZWxzIHZpYSBgaWRlbnRtYXBgCiAgcXVlcnlfc2V1b2JqJHJlZl9jZWxsdHlwZSA8LSBkZgogIHJldHVybihxdWVyeV9zZXVvYmopCn0KCmBgYAoKLS0tCiMjIGZ1bmN0aW9uIGluc3RhbmNlCiMjIyB0cmFpbgpgYGB7cn0KdW1hcHBsb3QoZHMyLCBncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiKQpJZGVudHMoZHMyKSA8LSBkczIkc2V1cmF0X2NsdXN0ZXJzCmJzdF9tb2RlbCA8LSBYR0Jvb3N0X3RyYWluX2Zyb21fc2V1b2JqKGRzMikKc2hvd190cmFpbl9sb3NzKGJzdF9tb2RlbCkKYGBgCgojIyBmdW5jdGlvbiBpbnN0YW5jZQojIyMgcHJlZGljdApgYGB7cn0KSWRlbnRzKGRzMCkgPC0gZHMwJHNldXJhdF9jbHVzdGVycwpkczAgPC0gWEdCb29zdF9wcmVkaWN0X2Zyb21fc2V1b2JqKGRzMCwgYnN0X21vZGVsID0gYnN0X21vZGVsKSAlPiUgCiAgcHJvamVjdDJyZWZfY2VsbHR5cGUocmVmX3NldW9iaiA9IGRzMiwgcmVmX2xhYmVscyA9IGMoInNldXJhdF9jbHVzdGVycyIsIkNsYXNzaWZpY2F0aW9uMSIpKQoKZHMxIDwtIFhHQm9vc3RfcHJlZGljdF9mcm9tX3NldW9iaihkczEsIGJzdF9tb2RlbCA9IGJzdF9tb2RlbCkgJT4lIAogIHByb2plY3QycmVmX2NlbGx0eXBlKHJlZl9zZXVvYmogPSBkczIsIHJlZl9sYWJlbHMgPSBjKCJzZXVyYXRfY2x1c3RlcnMiLCJDbGFzc2lmaWNhdGlvbjEiKSkKdW1hcHBsb3QoZHMwLCBncm91cC5ieSA9ICJyZWZfY2VsbHR5cGUiKQoKYGBgCiMjIHNjbWFwIHdyYXBwZXIKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NtYXApCgpta3JlZl9zY21hcF9mcm9tX3NldW9iaiA8LSBmdW5jdGlvbihyZWZfc2V1b2JqKXsKICAjIHJldHVybiBhIHJlZiBzY2Ugb2JqZWN0CiAgcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChyZWZfc2V1b2JqKQogIGxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCiAgCiAgY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChjb3VudHMocmVmX3NjZSkpCiAgbG9nY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChsb2djb3VudHMocmVmX3NjZSkpCiAgCiAgcm93RGF0YShyZWZfc2NlKSRmZWF0dXJlX3N5bWJvbCA8LSByb3duYW1lcyhyZWZfc2NlKQogIHJlZl9zY2UgPC0gcmVmX3NjZVshZHVwbGljYXRlZChyb3duYW1lcyhyZWZfc2NlKSksIF0KICByZWZfc2NlIDwtIHNlbGVjdEZlYXR1cmVzKHJlZl9zY2UsIHN1cHByZXNzX3Bsb3QgPSBUKSAlPiUgaW5kZXhDZWxsKCkKCiAgcmV0dXJuKHJlZl9zY2UpCn0KCnF1ZXJ5X3NjbWFwX2Zyb21fcmVmc2NlIDwtIGZ1bmN0aW9uKHF1ZXJ5X3NldW9iaiwgcmVmX3NjZSwgcmVmX2xhYmVscyA9ICdDbGFzc2lmaWNhdGlvbjEnKXsKICAjcmV0dXJuIGEgdXBkYXRlZCBzZXVyYXQgb2JqZWN0IHdpdGggbmV3IG1ldGFkYXRhIG5hbWVkIHNjbWFwX2lkZW50cwogIAogIHF1ZXJ5X3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChxdWVyeV9zZXVvYmopCiAgbG9nY291bnRzKHF1ZXJ5X3NjZSkgPC0gbG9nMihjb3VudHMocXVlcnlfc2NlKSArIDEpCiAgCiAgY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KGNvdW50cyhxdWVyeV9zY2UpKQogIGxvZ2NvdW50cyhxdWVyeV9zY2UpIDwtIGFzLm1hdHJpeChsb2djb3VudHMocXVlcnlfc2NlKSkKICAKICByb3dEYXRhKHF1ZXJ5X3NjZSkkZmVhdHVyZV9zeW1ib2wgPC0gcm93bmFtZXMocXVlcnlfc2NlKQogIHF1ZXJ5X3NjZSA8LSBxdWVyeV9zY2VbIWR1cGxpY2F0ZWQocm93bmFtZXMocXVlcnlfc2NlKSksIF0KICBxdWVyeV9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocXVlcnlfc2NlLCBzdXBwcmVzc19wbG90ID0gVCkgJT4lIGluZGV4Q2VsbCgpCiAgCiAgc2NtYXBDZWxsX3Jlc3VsdHMgPC0gc2NtYXBDZWxsKHF1ZXJ5X3NjZSwgbGlzdChyZWYgPSBtZXRhZGF0YShyZWZfc2NlKSRzY21hcF9jZWxsX2luZGV4KSkKICBzY21hcENlbGxfY2x1c3RlcnMgPC0gc2NtYXBDZWxsMkNsdXN0ZXIoc2NtYXBDZWxsX3Jlc3VsdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSlbW3JlZl9sYWJlbHNdXSkpKQogIAogIHF1ZXJ5X3NldW9iaiRzY21hcF9pZGVudHMgPC0gZGF0YS5mcmFtZShzY21hcENlbGxfY2x1c3RlcnMkc2NtYXBfY2x1c3Rlcl9sYWJzWywicmVmIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSBjb2xuYW1lcyhxdWVyeV9zZXVvYmopKQogIHJldHVybihxdWVyeV9zZXVvYmopCn0KYGBgCgpgYGB7cn0KcmVmX3NjZSA8LSBta3JlZl9zY21hcF9mcm9tX3NldW9iaihkczIpCmRzMCA8LSBxdWVyeV9zY21hcF9mcm9tX3JlZnNjZShkczAsIHJlZl9zY2UpCmRzMSA8LSBxdWVyeV9zY21hcF9mcm9tX3JlZnNjZShkczEsIHJlZl9zY2UpCnVtYXBwbG90KGRzMCwgZ3JvdXAuYnkgPSAic2NtYXBfaWRlbnRzIikKdW1hcHBsb3QoZHMwLCBncm91cC5ieSA9ICJyZWZfY2VsbHR5cGUiKQp0YWJsZShkczAkcmVmX2NlbGx0eXBlKQp0YWJsZShkczAkc2NtYXBfaWRlbnRzKQphZGp1c3RlZFJhbmRJbmRleChkczAkcmVmX2NlbGx0eXBlLCBkczAkc2V1cmF0X2NsdXN0ZXJzKQphZGp1c3RlZFJhbmRJbmRleChkczAkc2NtYXBfaWRlbnRzLCBkczAkc2V1cmF0X2NsdXN0ZXJzKQoKYGBgCgoKCiMjIHN1cGVydmlzZWQgdnMgdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcKIyMjIHVwc2V0IHBsb3QKIyMjIyBTTUMyCmBgYHtyfQojIGxpYnJhcnkoVXBTZXRSKQoKZGYgPC0gY2JpbmQoZHMxW1siQ2xhc3NpZmljYXRpb24xIl1dLGRzMVtbInJlZl9jZWxsdHlwZSJdXSxkczFbWyJzY21hcF9pZGVudHMiXV0pCmRmIDwtIGRmW2RmJENsYXNzaWZpY2F0aW9uMSA9PSAiU01DMiIgfCBkZiRyZWZfY2VsbHR5cGUgPT0gIlNNQzIiIHwgZGYkc2NtYXBfaWRlbnRzID09ICJTTUMyIiAsXQpsaSA8LSB0YWJsZShkZikgJT4lIGFzLmRhdGEuZnJhbWUoKSAj6I635b6X5ZCr5pyJU01DMueahGZyZXF1ZW5jeQpsaSA8LSBsaVshKGxpJEZyZXEgPCA1KSxdICPliKDpmaRmcmVxdWVuY3k8NeeahOihjApsaSA8LSBsaVtvcmRlcihsaSRGcmVxLGRlY3JlYXNpbmcgPSBUKSxdCmRkIDwtIGRhdGEuZnJhbWUobGksaW5kZXggPSBhcy5jaGFyYWN0ZXIoMTpucm93KGxpKSkpCgpkZCRpbmRleCA8LSBmYWN0b3IoZGQkaW5kZXgsbGV2ZWxzID0gMTpsZW5ndGgobGV2ZWxzKGRkJGluZGV4KSkpCmRkJEZyZXEgPC0gTlVMTApkZCA8LSByZXNoYXBlMjo6bWVsdChkZCxpZC52YXIgPSAiaW5kZXgiKQpjb2xuYW1lcyhkZCkgPC0gYygiaW5kZXgiLCJ0eXBlIiwibmFtZSIpCgpkdCA8LSBkYXRhLmZyYW1lKEZyZXEgPSBsaSRGcmVxLGluZGV4ID0gYXMuY2hhcmFjdGVyKDE6bnJvdyhsaSkpKQpkdCRpbmRleCA8LSBmYWN0b3IoZHQkaW5kZXgsbGV2ZWxzID0gMTpsZW5ndGgobGV2ZWxzKGR0JGluZGV4KSkpCmR0JGNvbCA8LSBkdCRGcmVxPihzdW0oZHQkRnJlcSkvMTApICNzZXQgdGhlIGNvbG9yIHRvIHJlZCBmb3IgdGhvc2UgZnJlcXVlbmN5ID4gMC4xCgpwMSA8LSBnZ3Bsb3QoZGQpK2dlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gaW5kZXgsIHkgPSB0eXBlLCBjb2xvciA9IG5hbWUpLCBzaXplID0gNikgKyBteXRoZW1lMiArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcnNfbGlzdCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQpwMiA8LSBnZ3Bsb3QoZHQsYWVzKHggPSBpbmRleCwgeSA9IEZyZXEsIGZpbGwgPSBjb2wpKStnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBteXRoZW1lMiArIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsInJlZCIpKQogCnBsb3QgPC0gY293cGxvdDo6cGxvdF9ncmlkKHAyLHAxLG5jb2wgPSAxLGFsaWduID0gJ3YnLHJlbF9oZWlnaHRzID0gYygyLDEpKQpwbG90CiMgZ2dzYXZlKCJTTUMyY2VsbHMuc3ZnIixwbG90PXBsb3QsZGV2aWNlID0gc3ZnLGhlaWdodCA9IDgsd2lkdGggPSA4KQpgYGAKCiMjIyMgU01DMQpgYGB7cn0KZGYgPC0gY2JpbmQoZHMxW1siQ2xhc3NpZmljYXRpb24xIl1dLGRzMVtbInJlZl9jZWxsdHlwZSJdXSxkczFbWyJzY21hcF9pZGVudHMiXV0pCmRmIDwtIGRmW2RmJENsYXNzaWZpY2F0aW9uMSA9PSAiU01DMSIgfCBkZiRyZWZfY2VsbHR5cGUgPT0gIlNNQzEiIHwgZGYkc2NtYXBfaWRlbnRzID09ICJTTUMxIiAsXQpsaSA8LSB0YWJsZShkZikgJT4lIGFzLmRhdGEuZnJhbWUoKSAj6I635b6X5ZCr5pyJU01DMeeahGZyZXF1ZW5jeQpsaSA8LSBsaVshKGxpJEZyZXEgPCA1KSxdICPliKDpmaRmcmVxdWVuY3k8NeeahOihjApsaSA8LSBsaVtvcmRlcihsaSRGcmVxLGRlY3JlYXNpbmcgPSBUKSxdCmRkIDwtIGRhdGEuZnJhbWUobGksaW5kZXggPSBhcy5jaGFyYWN0ZXIoMTpucm93KGxpKSkpCgpkZCRpbmRleCA8LSBmYWN0b3IoZGQkaW5kZXgsbGV2ZWxzID0gMTpsZW5ndGgobGV2ZWxzKGRkJGluZGV4KSkpCmRkJEZyZXEgPC0gTlVMTApkZCA8LSByZXNoYXBlMjo6bWVsdChkZCxpZC52YXIgPSAiaW5kZXgiKQpjb2xuYW1lcyhkZCkgPC0gYygiaW5kZXgiLCJ0eXBlIiwibmFtZSIpCgpkdCA8LSBkYXRhLmZyYW1lKEZyZXEgPSBsaSRGcmVxLGluZGV4ID0gYXMuY2hhcmFjdGVyKDE6bnJvdyhsaSkpKQpkdCRpbmRleCA8LSBmYWN0b3IoZHQkaW5kZXgsbGV2ZWxzID0gMTpsZW5ndGgobGV2ZWxzKGR0JGluZGV4KSkpCmR0JGNvbCA8LSBkdCRGcmVxPihzdW0oZHQkRnJlcSkvMTApICNzZXQgdGhlIGNvbG9yIHRvIHJlZCBmb3IgdGhvc2UgZnJlcXVlbmN5ID4gMC4xCgpwMSA8LSBnZ3Bsb3QoZGQpK2dlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gaW5kZXgsIHkgPSB0eXBlLCBjb2xvciA9IG5hbWUpLCBzaXplID0gNikgKyBteXRoZW1lMiArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcnNfbGlzdCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQpwMiA8LSBnZ3Bsb3QoZHQsYWVzKHggPSBpbmRleCwgeSA9IEZyZXEsIGZpbGwgPSBjb2wpKStnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBteXRoZW1lMiArIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsInJlZCIpKQogCnBsb3QgPC0gY293cGxvdDo6cGxvdF9ncmlkKHAyLHAxLG5jb2wgPSAxLGFsaWduID0gJ3YnLHJlbF9oZWlnaHRzID0gYygyLDEpKQpwbG90Cmdnc2F2ZSgiU01DMWNlbGxzLnN2ZyIscGxvdD1wbG90LGRldmljZSA9IHN2ZyxoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgojIyMjIEZCTQpgYGB7cn0KZGYgPC0gY2JpbmQoZHMxW1siQ2xhc3NpZmljYXRpb24xIl1dLGRzMVtbInJlZl9jZWxsdHlwZSJdXSxkczFbWyJzY21hcF9pZGVudHMiXV0pCmRmIDwtIGRmW2RmJENsYXNzaWZpY2F0aW9uMSA9PSAiRmlicm9teW9jeXRlIiB8IGRmJHJlZl9jZWxsdHlwZSA9PSAiRmlicm9teW9jeXRlIiB8IGRmJHNjbWFwX2lkZW50cyA9PSAiRmlicm9teW9jeXRlIiAsXQpsaSA8LSB0YWJsZShkZikgJT4lIGFzLmRhdGEuZnJhbWUoKSAj6I635b6X5ZCr5pyJRmlicm9teW9jeXRl55qEZnJlcXVlbmN5CmxpIDwtIGxpWyEobGkkRnJlcSA8IDUpLF0gI+WIoOmZpGZyZXF1ZW5jeTw155qE6KGMCmxpIDwtIGxpW29yZGVyKGxpJEZyZXEsZGVjcmVhc2luZyA9IFQpLF0KZGQgPC0gZGF0YS5mcmFtZShsaSxpbmRleCA9IGFzLmNoYXJhY3RlcigxOm5yb3cobGkpKSkKCmRkJGluZGV4IDwtIGZhY3RvcihkZCRpbmRleCxsZXZlbHMgPSAxOmxlbmd0aChsZXZlbHMoZGQkaW5kZXgpKSkKZGQkRnJlcSA8LSBOVUxMCmRkIDwtIHJlc2hhcGUyOjptZWx0KGRkLGlkLnZhciA9ICJpbmRleCIpCmNvbG5hbWVzKGRkKSA8LSBjKCJpbmRleCIsInR5cGUiLCJuYW1lIikKCmR0IDwtIGRhdGEuZnJhbWUoRnJlcSA9IGxpJEZyZXEsaW5kZXggPSBhcy5jaGFyYWN0ZXIoMTpucm93KGxpKSkpCmR0JGluZGV4IDwtIGZhY3RvcihkdCRpbmRleCxsZXZlbHMgPSAxOmxlbmd0aChsZXZlbHMoZHQkaW5kZXgpKSkKZHQkY29sIDwtIGR0JEZyZXE+KHN1bShkdCRGcmVxKS8xMCkgI3NldCB0aGUgY29sb3IgdG8gcmVkIGZvciB0aG9zZSBmcmVxdWVuY3kgPiAwLjEKCnAxIDwtIGdncGxvdChkZCkrZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBpbmRleCwgeSA9IHR5cGUsIGNvbG9yID0gbmFtZSksIHNpemUgPSA2KSArIG15dGhlbWUyICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbG9yc19saXN0KSArIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCnAyIDwtIGdncGxvdChkdCxhZXMoeCA9IGluZGV4LCB5ID0gRnJlcSwgZmlsbCA9IGNvbCkpK2dlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIG15dGhlbWUyICsgdGhlbWUoYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiwicmVkIikpCiAKcGxvdCA8LSBjb3dwbG90OjpwbG90X2dyaWQocDIscDEsbmNvbCA9IDEsYWxpZ24gPSAndicscmVsX2hlaWdodHMgPSBjKDIsMSkpCnBsb3QKZ2dzYXZlKCJGaWJyb215b2N5dGVjZWxscy5zdmciLHBsb3Q9cGxvdCxkZXZpY2UgPSBzdmcsaGVpZ2h0ID0gOCx3aWR0aCA9IDgpCmBgYAoKIyMgY29ycmVsYXRpb24gcGxvdApgYGB7cn0Kc2NtYXAgPC0gZHMxJHNjbWFwX2lkZW50cwp4Z2IgPC0gZHMxJHJlZl9jZWxsdHlwZQpyZWYgPC0gZHMyJENsYXNzaWZpY2F0aW9uMQoKZ2VuZXNldDEgPC0gcmVhZC50YWJsZSgiU01DIikKZHMxIDwtIEFkZE1vZHVsZVNjb3JlKGRzMSxmZWF0dXJlcyA9IGdlbmVzZXQxLCBuYW1lID0gJ1NNQ19zY29yZScpCmRzMiA8LSBBZGRNb2R1bGVTY29yZShkczIsZmVhdHVyZXMgPSBnZW5lc2V0MSwgbmFtZSA9ICdTTUNfc2NvcmUnKQpnZW5lc2V0MiA8LSByZWFkLnRhYmxlKCJGQiIpCmRzMSA8LSBBZGRNb2R1bGVTY29yZShkczEsZmVhdHVyZXMgPSBnZW5lc2V0MiwgbmFtZSA9ICdGQl9zY29yZScpCmRzMiA8LSBBZGRNb2R1bGVTY29yZShkczIsZmVhdHVyZXMgPSBnZW5lc2V0MiwgbmFtZSA9ICdGQl9zY29yZScpCiMgZigiU01DX3Njb3JlMSIsbGFiZWwgPSBGLCBkczEpICsgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdz0iIzFFOTBGRiIsIGhpZ2g9IiNmZjIxMjEiKQojIGYoIlNNQ19zY29yZTEiLGxhYmVsID0gRiwgZHMyKSArIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3c9IiMxRTkwRkYiLCBoaWdoPSIjZmYyMTIxIikKCmRmMSA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczEsIGMoIlNNQ19zY29yZTEiLCJGQl9zY29yZTEiKSwgY2VsbHMgPSBuYW1lcyhzY21hcFtzY21hcCA9PSAiRmlicm9teW9jeXRlIl0pKSx0eXBlID0gInNjbWFwIikgCmRmMiA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczEsYygiU01DX3Njb3JlMSIsIkZCX3Njb3JlMSIpLCBjZWxscyA9IG5hbWVzKHhnYlt4Z2IgPT0gIkZpYnJvbXlvY3l0ZSJdKSksdHlwZSA9ICJ4Z2J0cmVlIikgCmRmMyA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczIsYygiU01DX3Njb3JlMSIsIkZCX3Njb3JlMSIpLCBjZWxscyA9IG5hbWVzKHJlZltyZWYgPT0gIkZpYnJvbXlvY3l0ZSJdKSksdHlwZSA9ICJyZWZlcmVuY2UiKSAKCmRmIDwtIHJiaW5kKGRmMSxkZjIsZGYzKQoKZ2dib3hwbG90KGRmLCB4ID0gInR5cGUiLCB5ID0gIlNNQ19zY29yZTEiLCBjb2xvciA9ICJ0eXBlIiwgYWRkID0gImppdHRlciIpICsKICBzdGF0X2NvbXBhcmVfbWVhbnMoY29tcGFyaXNvbnMgPQogICAgICAgICAgICAgICAgICAgICAgIGxpc3QoYygic2NtYXAiLCJyZWZlcmVuY2UiKSxjKCJ4Z2J0cmVlIiwicmVmZXJlbmNlIikpLCAKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIndpbGNveC50ZXN0IikgCgpnZ2JveHBsb3QoZGYsIHggPSAidHlwZSIsIHkgPSAiRkJfc2NvcmUxIiwgY29sb3IgPSAidHlwZSIsIGFkZCA9ICJqaXR0ZXIiKSArCiAgc3RhdF9jb21wYXJlX21lYW5zKGNvbXBhcmlzb25zID0KICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGMoInNjbWFwIiwicmVmZXJlbmNlIiksYygieGdidHJlZSIsInJlZmVyZW5jZSIpKSwgCiAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJ3aWxjb3gudGVzdCIpIAoKZGYgPC0gZGF0YS5mcmFtZShyb3cubmFtZXMgPSBzZWxlY3RlZF9mZWF0dXJlcykKZm9yKGNlbGxfdHlwZSBpbiBjKCJTTUMxIiwiRmlicm9teW9jeXRlIiwiU01DMiIpKXsKICBkZjEgPC0gZGF0YS5mcmFtZShGZXRjaERhdGEoZHMxLCBzZWxlY3RlZF9mZWF0dXJlcywgY2VsbHMgPSBuYW1lcyhzY21hcFtzY21hcCA9PSBjZWxsX3R5cGVdKSkpICU+JSBjb2xNZWFucygpIAogIGRmMiA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczEsIHNlbGVjdGVkX2ZlYXR1cmVzLCBjZWxscyA9IG5hbWVzKHhnYlt4Z2IgPT0gY2VsbF90eXBlXSkpKSAlPiUgY29sTWVhbnMoKQogIGRmMyA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczIsIHNlbGVjdGVkX2ZlYXR1cmVzLCBjZWxscyA9IG5hbWVzKHJlZltyZWYgPT0gY2VsbF90eXBlXSkpKSAlPiUgY29sTWVhbnMoKQogIGRmW3Bhc3RlMCgicmVmXyIsY2VsbF90eXBlKV0gPC0gZGYzCiAgZGZbcGFzdGUwKCJ4Z2JfIixjZWxsX3R5cGUpXSA8LSBkZjIKICBkZltwYXN0ZTAoInNjbWFwXyIsY2VsbF90eXBlKV0gPC0gZGYxCn0KCmNvcnIgPC0gY29yKGRmKQpwaGVhdG1hcDo6cGhlYXRtYXAoY29ycixicmVha3MgPSB1bmlxdWUoYyhzZXEoMC42LCAxLCBsZW5ndGggPSAxMDApKSksCiAgICAgICAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIiMxRTkwRkYiLCAid2hpdGUiLCAiI2ZmMjEyMSIpKSgxMDApLAogICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLCBjbHVzdGVyX3Jvd3MgPSBULCBjbHVzdGVyX2NvbHMgPSBULAogICAgICAgIG1haW4gPSAiY29yciIsIGFuZ2xlX2NvbCA9IDQ1LCBzaG93X3Jvd25hbWVzID0gVCkKCmxpYnJhcnkoZ2djb3IpCnF1aWNrY29yKGRmLCB0eXBlID0gInVwcGVyIikgKyBnZW9tX2NpcmNsZTIoKQpgYGAKIyMjIFBDQQpgYGB7cn0Kc2VsZWN0ZWRfZmVhdHVyZXMgPC1pbnRlcnNlY3QoRmluZFZhcmlhYmxlRmVhdHVyZXMoZHMxLCBuZmVhdHVyZXMgPSAyMDApQGFzc2F5c1tbIlNDVCJdXUB2YXIuZmVhdHVyZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGRzMiwgbmZlYXR1cmVzID0gMjAwKUBhc3NheXNbWyJTQ1QiXV1AdmFyLmZlYXR1cmVzKQoKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gcmVhZC5jc3YoIi4vZGF0YXRhYmxlL2RzMl9mZWF0dXJlcy5jc3YiKQpzZWxlY3RlZF9mZWF0dXJlcyA8LSBhcy5jaGFyYWN0ZXIoc2VsZWN0ZWRfZmVhdHVyZXMkRmVhdHVyZVsxOjUwXSkKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gaW50ZXJzZWN0KHNlbGVjdGVkX2ZlYXR1cmVzLCBWYXJpYWJsZUZlYXR1cmVzKGRzMSkpCnJlcyA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IGMoIkEiLCJCIiwiQyIpKQoKZm9yKGNlbGxfdHlwZSBpbiBjKCJTTUMxIiwiRmlicm9teW9jeXRlIiwiU01DMiIpKXsKICBkZjEgPC0gZGF0YS5mcmFtZShGZXRjaERhdGEoZHMxLCBzZWxlY3RlZF9mZWF0dXJlcywgY2VsbHMgPSBuYW1lcyhzY21hcFtzY21hcCA9PSBjZWxsX3R5cGVdKSksdHlwZSA9ICJzY21hcCIpIAogIGRmMiA8LSBkYXRhLmZyYW1lKEZldGNoRGF0YShkczEsc2VsZWN0ZWRfZmVhdHVyZXMsIGNlbGxzID0gbmFtZXMoeGdiW3hnYiA9PSBjZWxsX3R5cGVdKSksdHlwZSA9ICJ4Z2J0cmVlIikgCiAgZGYzIDwtIGRhdGEuZnJhbWUoRmV0Y2hEYXRhKGRzMixzZWxlY3RlZF9mZWF0dXJlcywgY2VsbHMgPSBuYW1lcyhyZWZbcmVmID09IGNlbGxfdHlwZV0pKSx0eXBlID0gInJlZmVyZW5jZSIpIAogIAogIGRmIDwtIHJiaW5kKGRmMSxkZjIsZGYzKQogIAogIFBDQXJlcyA8LSBGYWN0b01pbmVSOjpQQ0EoZGZbLGMoLW5jb2woZGYpKV0sbmNwID0gNSwgZ3JhcGggPSBGKQogIAogIGRkIDwtIGNiaW5kKFBDQXJlc1tbImluZCJdXVtbImNvb3JkIl1dLGRhdGEuZnJhbWUodHlwZSA9IGRmWyxjKG5jb2woZGYpKV0pKQogIAogICMgZ2dwbG90KGRkKStnZW9tX3BvaW50KGFlcyh4ID0gRGltLjEsIHkgPSBEaW0uMiwgY29sb3IgPSB0eXBlKSkKICB2X3NjbWFwIDwtIGRkW2RkJHR5cGUgPT0gInNjbWFwIixjKDE6NSldICU+JSBjb2xNZWFucygpICU+JSBhcy5tYXRyaXgoKQogIHZfeGdiIDwtIGRkW2RkJHR5cGUgPT0gInhnYnRyZWUiLGMoMTo1KV0gJT4lIGNvbE1lYW5zKCkgJT4lIGFzLm1hdHJpeCgpCiAgdl9yZWYgPC0gZGRbZGQkdHlwZSA9PSAicmVmZXJlbmNlIixjKDE6NSldICU+JSBjb2xNZWFucygpICU+JSBhcy5tYXRyaXgoKQogIHYgPC0gYyhub3JtKHZfc2NtYXAtdl9yZWYpLG5vcm0odl94Z2Itdl9yZWYpLG5vcm0odl94Z2Itdl9zY21hcCkpCiAgcmVzW2NlbGxfdHlwZV0gPC0gdgp9CnJlcwpgYGAKCgojIyB1bnN1cGVydmlzZWQgdmVyc2lvbgpgYGB7cn0KdW1hcHBsb3QoZHMxKQp1bWFwcGxvdChkczIpCnVtYXBwbG90KGRzMCkKCmJzdF9tb2RlbCA8LSBYR0Jvb3N0X3RyYWluX2Zyb21fc2V1b2JqKGRzMCkKZHMyIDwtIFhHQm9vc3RfcHJlZGljdF9mcm9tX3NldW9iaihkczIsYnN0X21vZGVsLGNlbGx0eXBlX2Fzc2lnbiA9IDEpCiMgSWRlbnRzKGRzMikgPC0gZHMyJHNldXJhdF9jbHVzdGVycwpic3RfbW9kZWwgPC0gWEdCb29zdF90cmFpbl9mcm9tX3NldW9iaihkczIpCmRzMCA8LSBYR0Jvb3N0X3ByZWRpY3RfZnJvbV9zZXVvYmooZHMwLGJzdF9tb2RlbCxjZWxsdHlwZV9hc3NpZ24gPSAxKQoKdW1hcHBsb3QoZHMyLGdyb3VwLmJ5ID0gInByb2plY3RlZF9pZGVudHMiKQpjb25mdXNlX21hdHJpeCA8LSB0YWJsZShkczIkc2V1cmF0X2NsdXN0ZXJzLCBkczIkcHJvamVjdGVkX2lkZW50cywgZG5uPWMoInRydWUiLCJwcmUiKSkKCnNhbmtleV9wbG90KGNvbmZ1c2VfbWF0cml4ID0gY29uZnVzZV9tYXRyaXgsCiAgICAgICAgICAgIGRpbW5hbWVzKGNvbmZ1c2VfbWF0cml4KSRwcmUsZGltbmFtZXMoY29uZnVzZV9tYXRyaXgpJHRydWUsCiAgICAgICAgICAgIHNlc3Npb24gPSAiZHMyIGlzIHByb2plY3RlZCB0byBkczEiKQoKIyBjaG9vc2UgdHJhaW4gZGF0YXNldCAobWVyZ2VkKQoKZGYgPC0gY2JpbmQoZHMyW1sic2V1cmF0X2NsdXN0ZXJzIl1dLGRzMltbInByb2plY3RlZF9pZGVudHMiXV0pCmxpIDwtIHRhYmxlKGRmKSAlPiUgYXMuZGF0YS5mcmFtZSgpIApsaSA8LSBsaVshKGxpJEZyZXEgPCA1MCksXSAj5Yig6ZmkZnJlcXVlbmN5PDUw55qE57uG6IOe57G75Z6L57uE5ZCICmxpIDwtIGxpW29yZGVyKGxpJEZyZXEsZGVjcmVhc2luZyA9IFQpLF0KZGQgPC0gZGF0YS5mcmFtZShsaSxpbmRleCA9IGFzLmNoYXJhY3RlcigxOm5yb3cobGkpKSkKCgpjb25mdXNlX21hdHJpeFtjb25mdXNlX21hdHJpeDw1MF0gPC0gMCAj5Yig6ZmkZnJlcXVlbmN5PDUw55qE57uG6IOe57G75Z6LCmNvbmZ1c2VfbWF0cml4CgoKIyBmaW5kIGVtYmVkZGluZyBwYXR0ZXJuCnRlbXAgPC0gcHJvcC50YWJsZShjb25mdXNlX21hdHJpeCxtYXJnaW4gPSAxKSAjdW5zdXBlcnZpc2VkIGxhYmVsc+WFs+S6jnN1cGVydmlzZWQgbGFiZWxz55qE57uE5oiQCnRlbXAyIDwtIHByb3AudGFibGUoY29uZnVzZV9tYXRyaXgsbWFyZ2luID0gMikgI3N1cGVydmlzZWQgbGFiZWxz5YWz5LqOdW5zdXBlcnZpc2VkIGxhYmVsc+eahOe7hOaIkAoKdGVtcFtpcy5uYSh0ZW1wKV0gPC0gMAp0ZW1wMltpcy5uYSh0ZW1wMildIDwtIDAKCnRlbXBtIDwtIHJlc2hhcGUyOjptZWx0KHRlbXApCgp0ZCA8LSBjKCkKZm9yKGkgaW4gd2hpY2godGVtcD4wLjcpKXsKICBpZih0ZW1wMltpXSA8IDAuNyl7CiAgICB0ZCA8LSBhcHBlbmQodGQsaSkKICB9Cn0KdGVtcG0gPC0gdGVtcG1bdGQsXSAj5L+d5a2Y54K55a+5KHVuc3VwZXJ2aXNlZCBjbHVzdGVycywgc3VwZXJ2aXNlZCBjbHVzdGVycykKdGVtcG0kdmFsdWUgPC0gTlVMTAoKIyPkv67mlLnnlKjmnaXmnoTpgKDliJ3lp4vmoJHmqKHlnovnmoTmlbDmja7pm4YoZHMwKeWIhue+pApyZWZfaWRlbnRzIDwtIGRzMCRzZXVyYXRfY2x1c3RlcnMgI+acgOW8gOWni+eUqOadpeaehOmAoOagkeeahOWIhuexuwoKZm9yKGNlbGx0eXBlIGluIHRlbXBtJHRydWUpewogIG4gPC0gbmFtZXMoZHMwJHByb2plY3RlZF9pZGVudHNbZHMwJHByb2plY3RlZF9pZGVudHMgPT0gY2VsbHR5cGVdKSAjI+mcgOimgeaUueWPmOS4umVtYmVkZGluZ+eahOe7huiDngogIHJlZl9pZGVudHMgPC0gZmFjdG9yKHJlZl9pZGVudHMsIGxldmVscyA9IGMobGV2ZWxzKHJlZl9pZGVudHMpLDEwK2NlbGx0eXBlKSkKICByZWZfaWRlbnRzW25dIDwtIDEwK2NlbGx0eXBlCiAgCn0KSWRlbnRzKGRzMCkgPC0gcmVmX2lkZW50cwoKCiMj55So5p2l5L+u5q2j5qCR55qE5pWw5o2u6ZuGKGRzMinliIbnvqQKbW9kX2lkZW50cyA8LSBkczIkcHJvamVjdGVkX2lkZW50cwpmb3IoY2VsbHR5cGUgaW4gdGVtcG0kdHJ1ZSl7CiAgbiA8LSBuYW1lcyhkczIkc2V1cmF0X2NsdXN0ZXJzW2RzMiRzZXVyYXRfY2x1c3RlcnMgPT0gY2VsbHR5cGVdKSAjI+mcgOimgeaUueWPmOS4umVtYmVkZGluZ+eahOe7huiDngogIG1vZF9pZGVudHMgPC0gZmFjdG9yKG1vZF9pZGVudHMsIGxldmVscyA9IGMobGV2ZWxzKG1vZF9pZGVudHMpLDEwK2NlbGx0eXBlKSkKICBtb2RfaWRlbnRzW25dIDwtIDEwK2NlbGx0eXBlCn0KIyMjIGRzMueahOeLrOeJuee7huiDnuexu+Wei++8jOWIpOaWrWVtYmVkZGluZ+aooeW8jwoKZGYgPC0gdGFibGUoZHMwJHByb2plY3RlZF9pZGVudHMpICU+JSBwcm9wLnRhYmxlKCkKdW5pcXVlX2NlbGx0eXBlcyA8LSBhcy5udW1lcmljKG5hbWVzKGRmW2RmPDAuMDFdKSkgI+aKleWwhOS5i+WQjuWwkeS6jjEl77yM5L2/55So5peg55uR552j5YiG576kCiMgdGFibGUoZHMwJHByb2plY3RlZF9pZGVudHMpCmZvcihjZWxsdHlwZSBpbiB1bmlxdWVfY2VsbHR5cGVzKXsKICBuIDwtIG5hbWVzKGRzMiRzZXVyYXRfY2x1c3RlcnNbZHMyJHNldXJhdF9jbHVzdGVycyA9PSBjZWxsdHlwZV0pICMj6ZyA6KaB5pS55Y+Y5Li6ZW1iZWRkaW5n55qE57uG6IOeCiAgbW9kX2lkZW50cyA8LSBmYWN0b3IobW9kX2lkZW50cywgbGV2ZWxzID0gYyhsZXZlbHMobW9kX2lkZW50cyksMjArY2VsbHR5cGUpKQogIG1vZF9pZGVudHNbbl0gPC0gMjArY2VsbHR5cGUKfQoKCiMgdW1hcHBsb3QoZHMyLCJwcm9qZWN0ZWRfaWRlbnRzIikKSWRlbnRzKGRzMikgPC0gbW9kX2lkZW50cwoKbWVyZ2VkcyA8LSBtZXJnZShkczAsc3Vic2V0KGRzMixpZGVudHMgPSAidW5hc3NpZ25lZCIsaW52ZXJ0ID0gVCkpCmxldmVscyhJZGVudHMobWVyZ2VkcykpIDwtIGMoMDoobGVuZ3RoKGxldmVscyhJZGVudHMobWVyZ2VkcykpKS0xKSkgIyPph43lkb3lkI3lm6DlrZDmsLTlubMKc2V1b2JqX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhtZXJnZWRzKSkpICPljp/lp4vorq3nu4PmlbDmja4KCnRlbXAgPC0gYXMubWF0cml4KEdldEFzc2F5RGF0YShtZXJnZWRzLCBzbG90ID0gImRhdGEiKSkKZ2VuZWxpc3QgPC0gR2V0QXNzYXlEYXRhKGRzMCwgc2xvdCA9ICJ2YXIuZmVhdHVyZXMiKQp0ZW1wIDwtIHRlbXBbZ2VuZWxpc3QsIF0gIyDov4fmu6Tpq5jlj5jlvILln7rlm6AKCnNldW9ial9kYXRhIDwtIG1hdHJpeChkYXRhID0gMCwgbnJvdyA9IGJzdF9tb2RlbCRuZmVhdHVyZXMsIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChic3RfbW9kZWxbWyJmZWF0dXJlX25hbWVzIl1dLGNvbG5hbWVzKHRlbXApKSkKaW50ZXJzZWN0X2ZlYXR1cmVzIDwtIGludGVyc2VjdChic3RfbW9kZWxbWyJmZWF0dXJlX25hbWVzIl1dLCByb3duYW1lcyh0ZW1wKSkKc2V1b2JqX2RhdGFbaW50ZXJzZWN0X2ZlYXR1cmVzLF0gPC0gdGVtcFtpbnRlcnNlY3RfZmVhdHVyZXMsXQpybSh0ZW1wKQoKIyMg6aKd5aSW5aKe5Yqg55qE57uG6IOe5L+h5oGvCgp4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhtZXJnZWRzKSkpLAogICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQoKCnNldW9ial90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoc2V1b2JqX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBzZXVvYmpfbGFiZWwpIAojIHVzZSB3aG9sZSBkYXRhc2V0IGFzIHRyYWluIGRhdGEKc2V1b2JqX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBzZXVvYmpfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gc2V1b2JqX3RyYWluX2RhdGEkbGFiZWwpCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBzZXVvYmpfdHJhaW4sIG5yb3VuZHMgPSA1MCwgdmVyYm9zZSA9IDApCgoKZHMwIDwtIFhHQm9vc3RfcHJlZGljdF9mcm9tX3NldW9iaihkczAsYnN0X21vZGVsKQpkczEgPC0gWEdCb29zdF9wcmVkaWN0X2Zyb21fc2V1b2JqKGRzMSxic3RfbW9kZWwpCmRzMiA8LSBYR0Jvb3N0X3ByZWRpY3RfZnJvbV9zZXVvYmooZHMyLGJzdF9tb2RlbCkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9OH0KdW1hcHBsb3QoZHMwLCJwcm9qZWN0ZWRfaWRlbnRzIikgLwp1bWFwcGxvdChkczEsInByb2plY3RlZF9pZGVudHMiKSAvCnVtYXBwbG90KGRzMiwicHJvamVjdGVkX2lkZW50cyIpCgoKbGV2ZWxzKGRzMCRwcm9qZWN0ZWRfaWRlbnRzKQpgYGAKCmBgYHtyfQojIG1lcmdlZHMgPC0gbWVyZ2VkcyAlPiUgU0NUcmFuc2Zvcm0odmFycy50by5yZWdyZXNzID0gInBlcmNlbnQubXQiLCB2ZXJib3NlID0gRikgJT4lIAojICAgICBSdW5QQ0EoKSAlPiUgRmluZE5laWdoYm9ycyhkaW1zID0gMToyMCkgJT4lIAojICAgICBSdW5VTUFQKGRpbXMgPSAxOjIwKSAlPiUgCiMgICAgIEZpbmRDbHVzdGVycyhyZXNvbHV0aW9uID0gMC4xKQojIAojIHVtYXBwbG90KG1lcmdlZHMsZ3JvdXAuYnkgPSAiQ2xhc3NpZmljYXRpb24xIiAsc3BsaXQuYnkgPSAib3JpZy5pZGVudCIpCmBgYAoKCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLgoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgo=